package io.flutter.plugins.camera;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.graphics.PixelFormat;
import android.hardware.camera2.CameraAccessException;
import android.media.MediaActionSound;
import android.media.SoundPool;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;

import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;

import org.jetbrains.annotations.Nullable;

import java.util.Objects;

import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.camera.features.CameraFeatureFactoryImpl;
import io.flutter.plugins.camera.features.Point;
import io.flutter.plugins.camera.features.autofocus.FocusMode;
import io.flutter.plugins.camera.features.exposurelock.ExposureMode;
import io.flutter.plugins.camera.features.flash.FlashMode;
import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
import io.flutter.view.TextureRegistry;

public class CameraService extends Service {

    private String createNotificationChannel() {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String channelId = getPackageName();
            NotificationChannel channel = new NotificationChannel(channelId,
                    "后台录像", NotificationManager.IMPORTANCE_HIGH);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);

            NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            service.createNotificationChannel(channel);
            return channelId;
        } else {
            return "";
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new CameraBinder();
    }

    private int WIDTH = 0, STYLE;
    private View mSurfaceView;
    private WindowManager windowManager;
    private boolean isRunning;

    public void setOverlayWidth(int w, int style) {
        Log.i("TAG", "CameraService overlay width=" + w + ", SurfaceView=" + (mSurfaceView != null));
        WIDTH = w;
        STYLE = style;
        buildOverlay();
    }

    @Nullable
    private Camera camera;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("TAG", "CameraService onCreate...");
        isRunning = true;

        String channelId = createNotificationChannel();
        Notification notification = new NotificationCompat.Builder(this, channelId)
                .setContentTitle("正在后台录制视频")
//                .setSmallIcon()
                .build();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            startForeground(5432, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA);
        } else {
            startForeground(5432, notification);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        isRunning = false;
       releaseWindow();
    }

    private void releaseWindow() {
        ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
        if(mSurfaceView != null) {
            windowManager.removeView(mSurfaceView);
            mSurfaceView = null;
        }
    }

    private void buildOverlay() {
        if(STYLE == 0) WIDTH = 0;
        if (WIDTH > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if(Settings.canDrawOverlays(this) && mSurfaceView == null) {
                if(STYLE == 2 || Build.PRODUCT.endsWith("-AL00")) { //华为 nova9 nova10,不支持 3个以上的 surface
                    mSurfaceView = new ImageView(this);
                    mSurfaceView.setBackgroundColor(0x7fff0000);
                    WIDTH = 30;
                } else {
                    mSurfaceView = new SurfaceView(this);
                }
                WindowManager.LayoutParams p = new WindowManager.LayoutParams(WIDTH, WIDTH,
                        type(),
                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                        PixelFormat.TRANSLUCENT);
                p.gravity = Gravity.TOP|Gravity.LEFT;

                windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
                windowManager.addView(mSurfaceView, p);
            }
        }
    }

    static int type() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            return 2003;//WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        }
    }

    public void onMethodCall(MethodCall call, final MethodChannel.Result result, Activity activity, EventChannel imageStreamChannel) {
        switch (call.method) {
            case "initialize":
            {
                if (camera != null) {
                    try {
                        float r = camera.open(call.argument("imageFormatGroup"));
                        Log.e("TAG", "width/height= " + r);

                        if(mSurfaceView != null) {
                            WindowManager.LayoutParams params = (WindowManager.LayoutParams) mSurfaceView.getLayoutParams();
                            params.height = (int) (WIDTH * r);
                            params.width = WIDTH;
                            windowManager.updateViewLayout(mSurfaceView, params);
                        }
                        result.success(null);
                    } catch (Exception e) {
                        handleException(e, result);
                    }
                } else {
                    result.error(
                            "cameraNotFound",
                            "Camera not found. Please call the 'create' method before calling 'initialize'.",
                            null);
                }
                break;
            }
            case "takePicture":
            {
                boolean sound = call.argument("sound");
                if(sound) {
                    try {
                        MediaActionSound soundEffect = new MediaActionSound();
                        soundEffect.play(MediaActionSound.SHUTTER_CLICK);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                camera.takePicture(result,call.argument("filePath"),call.argument("orientation"));
                break;
            }
            case "startVideoRecording":
            {
                camera.startVideoRecording(
                        result,
                        Objects.equals(call.argument("enableStream"), true) ? imageStreamChannel : null,
                        call.argument("filePath"), call.argument("videoBitRate"),call.argument("videoFrameRate"));
                break;
            }
            case "waitVideoRecording":
            {
                ///等待回调用的参数
                camera.flutterVideoResult = result;
                break;
            }
            case "stopVideoRecording":
            {
                camera.stopVideoRecording(result);
                break;
            }
            case "pauseVideoRecording":
            {
                camera.pauseVideoRecording(result);
                break;
            }
            case "resumeVideoRecording":
            {
                camera.resumeVideoRecording(result);
                break;
            }
            case "setFlashMode":
            {
                String modeStr = call.argument("mode");
                FlashMode mode = FlashMode.getValueForString(modeStr);
                if (mode == null) {
                    result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null);
                    return;
                }
                try {
                    camera.setFlashMode(result, mode);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "setExposureMode":
            {
                String modeStr = call.argument("mode");
                ExposureMode mode = ExposureMode.getValueForString(modeStr);
                if (mode == null) {
                    result.error("setExposureModeFailed", "Unknown exposure mode " + modeStr, null);
                    return;
                }
                try {
                    camera.setExposureMode(result, mode);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "setExposurePoint":
            {
                Boolean reset = call.argument("reset");
                Double x = null;
                Double y = null;
                if (reset == null || !reset) {
                    x = call.argument("x");
                    y = call.argument("y");
                }
                try {
                    camera.setExposurePoint(result, new Point(x, y));
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "getMinExposureOffset":
            {
                try {
                    result.success(camera.getMinExposureOffset());
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "getMaxExposureOffset":
            {
                try {
                    result.success(camera.getMaxExposureOffset());
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "getExposureOffsetStepSize":
            {
                try {
                    result.success(camera.getExposureOffsetStepSize());
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "setExposureOffset":
            {
                try {
                    camera.setExposureOffset(result, call.argument("offset"));
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "setFocusMode":
            {
                String modeStr = call.argument("mode");
                FocusMode mode = FocusMode.getValueForString(modeStr);
                if (mode == null) {
                    result.error("setFocusModeFailed", "Unknown focus mode " + modeStr, null);
                    return;
                }
                try {
                    camera.setFocusMode(result, mode);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "setFocusPoint":
            {
                Boolean reset = call.argument("reset");
                Double x = null;
                Double y = null;
                if (reset == null || !reset) {
                    x = call.argument("x");
                    y = call.argument("y");
                }
                try {
                    camera.setFocusPoint(result, new Point(x, y));
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "startImageStream":
            {
                try {
                    camera.startPreviewWithImageStream(imageStreamChannel);
                    result.success(null);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "stopImageStream":
            {
                try {
                    camera.startPreview();
                    result.success(null);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "getMaxZoomLevel":
            {
                assert camera != null;

                try {
                    float maxZoomLevel = camera.getMaxZoomLevel();
                    result.success(maxZoomLevel);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "getMinZoomLevel":
            {
                assert camera != null;

                try {
                    float minZoomLevel = camera.getMinZoomLevel();
                    result.success(minZoomLevel);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "setZoomLevel":
            {
                assert camera != null;

                Double zoom = call.argument("zoom");

                if (zoom == null) {
                    result.error(
                            "ZOOM_ERROR", "setZoomLevel is called without specifying a zoom level.", null);
                    return;
                }

                try {
                    camera.setZoomLevel(result, zoom.floatValue());
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "lockCaptureOrientation":
            {
                PlatformChannel.DeviceOrientation orientation =
                        CameraUtils.deserializeDeviceOrientation(call.argument("orientation"));

                try {
                    camera.lockCaptureOrientation(orientation);
                    result.success(null);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "unlockCaptureOrientation":
            {
                try {
                    camera.unlockCaptureOrientation();
                    result.success(null);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "pausePreview":
            {
                try {
                    camera.pausePreview();
                    result.success(null);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "resumePreview":
            {
                camera.resumePreview();
                result.success(null);
                break;
            }
            case "setDescriptionWhileRecording":
            {
                try {
                    String cameraName = call.argument("cameraName");
                    CameraProperties cameraProperties =
                            new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity));
                    camera.setDescriptionWhileRecording(result, cameraProperties);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "dispose":
            {
                closeCamera();
                if (camera != null) {
                    camera.dispose();
                }
                releaseWindow();
                stopSelf();
                isRunning = false;
                result.success(null);
                break;
            }
            default:
                result.notImplemented();
                break;
        }
    }

    public boolean isRecordingVideo() {
        return camera != null && camera.recordingVideo;
    }

    public void closeCamera() {
        if (camera != null) {
            camera.close();
        }
    }

    public void create(final Activity activity,
                       final TextureRegistry.SurfaceTextureEntry flutterTexture,
                       final DartMessenger dartMessenger,
                       final CameraProperties cameraProperties,
                       final ResolutionPreset resolutionPreset,
                       final boolean enableAudio) {
        camera =
                new Camera(
                        activity,
                        flutterTexture,
                        mSurfaceView instanceof SurfaceView ? ((SurfaceView) mSurfaceView).getHolder().getSurface() : null,
                        new CameraFeatureFactoryImpl(),
                        dartMessenger,
                        cameraProperties,
                        resolutionPreset,
                        enableAudio);
    }


    // We move catching CameraAccessException out of onMethodCall because it causes a crash
    // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to
    // to be able to compile with <21 sdks for apps that want the camera and support earlier version.
    @SuppressWarnings("ConstantConditions")
    public static void handleException(Exception exception, MethodChannel.Result result) {
        if (exception instanceof CameraAccessException) {
            result.error("CameraAccess", exception.getMessage(), null);
            return;
        }

        // CameraAccessException can not be cast to a RuntimeException.
        throw (RuntimeException) exception;
    }

    public class CameraBinder extends Binder {
        public CameraService get() {
            return  CameraService.this;
        }
    }
}
